﻿using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace HostingRedisServer
{
    public class RedisServer : IRedisServer
    {
        private Process? _redisProcess;
        private readonly ILogger<RedisServer> _logger;
        private readonly RedisOption _redis;
        public string ConnectionString { get; set; }
        private bool _disposed = false;
        private IntPtr _jobHandle = IntPtr.Zero;

        public RedisServer(ILogger<RedisServer> logger, IOptionsMonitor<RedisOption> _redisOption)
        {
            _logger = logger;
            _redis = _redisOption.CurrentValue;
            ConnectionString = $"{_redisOption.CurrentValue.Host}:{_redisOption.CurrentValue.Port}";
        }

        public async Task StartAsync()
        {
            try
            {
                var redisPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RedisServer", "redis-server.exe");
                if (!File.Exists(redisPath))
                {
                    throw new FileNotFoundException($"Redis 服务器未找到: {redisPath}");
                }

                // 创建 Job Object，确保子进程随父进程退出
                _jobHandle = CreateJobObject(IntPtr.Zero, null);
                if (_jobHandle == IntPtr.Zero)
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }

                // 配置 Job：当父进程退出时，自动终止所有子进程
                var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
                {
                    LimitFlags = 0x2000 // JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
                };

                var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
                {
                    BasicLimitInformation = info
                };

                int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
                IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
                try
                {
                    Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
                    if (!SetInformationJobObject(_jobHandle, JobObjectInfoType.ExtendedLimitInformation,
                        extendedInfoPtr, (uint)length))
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(extendedInfoPtr);
                }

                _redisProcess = new Process
                {
                    StartInfo = new ProcessStartInfo
                    {
                        FileName = redisPath,
                        Arguments = $"--port {_redis.Port}",
                        UseShellExecute = false,
                        CreateNoWindow = true,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true
                    },
                    EnableRaisingEvents = true
                };

                _redisProcess.OutputDataReceived += (sender, e) =>
                {
                    if (!string.IsNullOrEmpty(e.Data))
                        _logger.LogInformation("Redis: {Output}", e.Data);
                };

                _redisProcess.ErrorDataReceived += (sender, e) =>
                {
                    if (!string.IsNullOrEmpty(e.Data))
                        _logger.LogError("Redis Error: {Error}", e.Data);
                };

                _redisProcess.Start();

                // 将进程添加到 Job Object
                if (!AssignProcessToJobObject(_jobHandle, _redisProcess.Handle))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }

                _redisProcess.BeginOutputReadLine();
                _redisProcess.BeginErrorReadLine();

                _logger.LogInformation("Redis 服务器启动在: {ConnectionString}, PID: {ProcessId}",
                    ConnectionString, _redisProcess.Id);

                await Task.Delay(2000);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "启动 Redis 服务器失败");
                CleanupJobObject();
                throw;
            }
        }


        public async Task StopAsync()
        {
            if (_redisProcess == null)
            {
                _logger.LogInformation("Redis 进程不存在");
                return;
            }

            try
            {
                if (_redisProcess.HasExited)
                {
                    _logger.LogInformation("Redis 进程已退出");
                    return;
                }

                _logger.LogInformation("正在停止 Redis 服务器 (PID: {ProcessId})...", _redisProcess.Id);

                // 方法1: 尝试优雅关闭
                try
                {
                    await SendRedisShutdownCommand();
                    await Task.Delay(1000);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "发送 SHUTDOWN 命令失败");
                }

                // 方法2: 强制终止
                if (!_redisProcess.HasExited)
                {
                    _redisProcess.Kill(true);
                    var waitTask = Task.Run(() => _redisProcess.WaitForExit(5000));
                    await waitTask;

                    if (!_redisProcess.HasExited)
                    {
                        _logger.LogError("Redis 进程仍在运行，尝试终止端口占用进程");
                        KillProcessByPort(_redis.Port);
                    }
                    else
                    {
                        _logger.LogInformation("Redis 服务器已停止");
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "停止 Redis 服务器时发生错误");
            }
            finally
            {
                CleanupJobObject();
            }
        }

        private async Task SendRedisShutdownCommand()
        {
            using var client = new System.Net.Sockets.TcpClient();
            await client.ConnectAsync(_redis.Host, _redis.Port);
            using var stream = client.GetStream();
            var shutdownCommand = Encoding.UTF8.GetBytes("SHUTDOWN NOSAVE\r\n");
            await stream.WriteAsync(shutdownCommand, 0, shutdownCommand.Length);
        }

        private void KillProcessByPort(int port)
        {
            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = "cmd.exe",
                    Arguments = $"/c for /f \"tokens=5\" %a in ('netstat -ano ^| findstr :{port}') do taskkill /F /PID %a",
                    UseShellExecute = false,
                    CreateNoWindow = true,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true
                };

                using var process = Process.Start(psi);
                process?.WaitForExit(3000);
                _logger.LogInformation("已尝试通过端口终止进程");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "通过端口终止进程失败");
            }
        }

        private void CleanupJobObject()
        {
            if (_jobHandle != IntPtr.Zero)
            {
                CloseHandle(_jobHandle);
                _jobHandle = IntPtr.Zero;
            }
        }

        public void Dispose()
        {
            if (_disposed) return;

            try
            {
                var stopTask = StopAsync();
                if (!stopTask.Wait(TimeSpan.FromSeconds(10)))
                {
                    _logger.LogError("停止 Redis 超时");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Dispose 时停止 Redis 失败");
            }
            finally
            {
                _redisProcess?.Dispose();
                CleanupJobObject();
                _disposed = true;
            }
        }

        #region Windows API

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string? lpName);

        [DllImport("kernel32.dll")]
        private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType,
            IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);

        private enum JobObjectInfoType
        {
            AssociateCompletionPortInformation = 7,
            BasicLimitInformation = 2,
            BasicUIRestrictions = 4,
            EndOfJobTimeInformation = 6,
            ExtendedLimitInformation = 9,
            SecurityLimitInformation = 5,
            GroupInformation = 11
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct JOBOBJECT_BASIC_LIMIT_INFORMATION
        {
            public long PerProcessUserTimeLimit;
            public long PerJobUserTimeLimit;
            public uint LimitFlags;
            public UIntPtr MinimumWorkingSetSize;
            public UIntPtr MaximumWorkingSetSize;
            public uint ActiveProcessLimit;
            public UIntPtr Affinity;
            public uint PriorityClass;
            public uint SchedulingClass;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct IO_COUNTERS
        {
            public ulong ReadOperationCount;
            public ulong WriteOperationCount;
            public ulong OtherOperationCount;
            public ulong ReadTransferCount;
            public ulong WriteTransferCount;
            public ulong OtherTransferCount;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
        {
            public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
            public IO_COUNTERS IoInfo;
            public UIntPtr ProcessMemoryLimit;
            public UIntPtr JobMemoryLimit;
            public UIntPtr PeakProcessMemoryUsed;
            public UIntPtr PeakJobMemoryUsed;
        }

        #endregion
    }
}
